﻿namespace StyleCop.CSharp.JslRefactoringRules
{
    using System;
    using System.Globalization;
    using System.Linq;

    /// <summary>
    /// Finds variable being that could be set in their declaration.
    /// </summary>
    public class LocalVariablesShouldBeSetWhereTheyAreDeclared : CsRuleBase<RuleAnalyzer>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LocalVariablesShouldBeSetWhereTheyAreDeclared"/> class.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        /// <param name="document">The document.</param>
        public LocalVariablesShouldBeSetWhereTheyAreDeclared(RuleAnalyzer analyzer, CsDocument document)
            : base(analyzer)
        {
            this.Document = document;
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Analyzes the graph.
        /// </summary>
        /// <param name="element">The element to graph.</param>
        /// <param name="graph">The graph.</param>
        public override void AnalyzeGraph(CsElement element, CodeUnitGraph graph)
        {
            if (this.Analyzer.IsRuleEnabled(this.Document, RuleAnalyzer.LocalVariablesShouldBeSetWhereTheyAreDeclared))
            {
                this.CheckForAssigningInDeclarator(graph);
            }
        }
        #endregion Public Methods

        #region Private Methods
        private void CheckForAssigningInDeclarator(CodeUnitGraph graph)
        {
            // Skip variables that variable declarations that are initialized and ones that are not variable
            // declaration statements.
            var variableDeclarators =
                from variableDeclarator in graph.Vertices.OfType<VariableDeclaratorExpression>()
                where (variableDeclarator.Initializer == null)
                    && (variableDeclarator.ParentStatement() is VariableDeclarationStatement)
                select variableDeclarator;

            // Process the variable declarators that may be wrong.
            foreach (var variableDeclarator in variableDeclarators)
            {
                this.ProcessVariableDeclarator(graph, variableDeclarator);
            }
        }

        private void ProcessVariableDeclarator(CodeUnitGraph graph, VariableDeclaratorExpression variableDeclarator)
        {
            LiteralExpression assignmentThatShouldBeMoved = null;
            var variable = variableDeclarator.GetVariable();
            var result = CodeUnitGraphHelper.SearchChildCodePaths(
                graph,
                variableDeclarator.Identifier,
                codeUnit =>
                {
                    // If this is a LiteralExpression then it may be a usage of the variable.
                    LiteralExpression literalExpression = codeUnit as LiteralExpression;
                    if (literalExpression != null)
                    {
                        var variableUsageInfo = literalExpression.GetVariableUsage();
                        if (variableUsageInfo != null)
                        {
                            if (object.ReferenceEquals(variable, variableUsageInfo.Variable))
                            {
                                switch (variableUsageInfo.Usage)
                                {
                                    case VariableUsage.Assignment:
                                        if (object.ReferenceEquals(literalExpression.ParentStatement().Parent, variableDeclarator.ParentStatement().Parent))
                                        {
                                            assignmentThatShouldBeMoved = literalExpression;
                                            return GraphSearchState.Found;
                                        }
                                        else
                                        {
                                            return GraphSearchState.NotFound;
                                        }

                                    case VariableUsage.OutParameter:
                                        // The variable was set in an out parameter so it can't be moved to the declarator.
                                        return GraphSearchState.NotFound;

                                    default:
                                        throw new InvalidOperationException(
                                            string.Format(
                                                CultureInfo.CurrentCulture,
                                                "The variable '{0}' was used unexpectedly at position {1}.",
                                                variable.Name,
                                                literalExpression.Location.HumanReadable()));
                                }
                            }
                        }
                    }

                    return GraphSearchState.ContinuePath;
                });

            if (result == GraphSearchState.Found)
            {
                this.AddViolation(
                    variableDeclarator.ParentElement(),
                    assignmentThatShouldBeMoved.LineNumber,
                    RuleAnalyzer.LocalVariablesShouldBeSetWhereTheyAreDeclared,
                    variableDeclarator.Identifier.Text);
            }
        }
        #endregion Private Methods
    }
}